package com.android.kalaha;

import java.util.ArrayList;
import java.util.Iterator;
import android.util.Log;

import android.widget.Button;

/**
 * Cupboard (Spielbrett) Class
 * 
 * @author nik
 * 
 */
public class Cupboard extends Global {

	// PREFERENCES
	private Integer cfg_starting_beans = 4;
	private Boolean cfg_early_showdown = false;

	// RUNTIME VARS
	private ArrayList<Cup> cups = new ArrayList<Cup>();
	private ArrayList<Player> players = new ArrayList<Player>();
	private String message = "";
	private Integer current_player = -1;
	private Boolean game_over = true;
	private Boolean random_mode = true;

	public Launch activity;
	
	// Evaluation Vars
	public Evaluation evaluation = null;
	
	
	/**
	 * Constructur
	 * @param evaluation
	 */
	public Cupboard() {
		super();
		this.evaluation = new Evaluation(this);
	}
	public Cupboard(Launch launch) {
		super();
		this.evaluation = new Evaluation(this);
		this.activity = launch;
	}

	/**
	 * Main game routine --// The function to empty a cup and distribute the
	 * beans to the following cups
	 * 
	 * @param cup
	 *            , the cup, which should be taken
	 * @return boolean, was it a valid move?
	 */
	public Boolean take_cup(Cup cup) {

		Integer handofbeans;

		// take all beans of the cup away
		handofbeans = cup.fetch_beans();

		// check if cup was empty (this is not allowed => error msg)
		if (handofbeans == 0) {
			this.message = "No beans in the cup, stupid";
			return false; // error: cup is empty
		}

		// check if game is started yet
		// otherwise the cups should be disabled.
		if (this.game_over) {
			Log.d("ERROR", "No Game is started");
			this.message = "Click on 'Start' to start a game";
			return false;
		}

		// check if its own cup
		if (this.is_own_cup(cup) == false) {
			return false;
		}

		// Ok so far: Do the move...
		cup = do_the_move(cup, handofbeans);

		// Hijacking: if last cup is empty, bring the beans of the opposite cup
		// to own can
		this.check_to_steal_beans(cup);

		// Is sombody out of beans?
		boolean running_out_of_beans = this.check_if_running_out_of_beans();

		// Is there is a winner?
		if (running_out_of_beans || this.cfg_early_showdown) {
			if(this.check_for_winner()){
				return true;
			}
		}

		// Is there a Draw?
		if(this.check_for_draw()){
			return true;
		}

		
		// Change active player if necessary
		this.check_for_changing_player(cup);

		// OK. Was a valid move.
		return true;
	}

	
	
	/**
	 * @param cup
	 * @param handofbeans
	 * @return
	 */
	private Cup do_the_move(Cup cup, Integer handofbeans) {

		// distribute all beans from the hand to the following cups
		this.debug("Ok, start distributing...");
		Integer cup_id = cup.id;
		for (Integer i = 0; i < handofbeans; i++) {

			// move to next cup in the row
			cup_id++;
			if (cup_id > 13) {
				cup_id = 0;
			}
			cup = this.cups.get(cup_id);

			// No beans for opponents can. Check for that and skip if required
			Player opponent = this.get_opponent_player(null);
			if (opponent.get_can().equals(cup)) {
				handofbeans++;
				continue;
			}

			// add one bean to current cup
			cup.add_bean();
		}
		this.debug("beans are distributed...");
		return cup;
	}

	
	/**
	 * Start a new Game: reset cupboard
	 */
	public void newgame() {
		// Setup a new game and start the machine

		// reset cupboard
		this.game_over = false;

		// reset each cup
		Iterator<Cup> itr = this.cups.iterator();
		while (itr.hasNext()) {
			Cup cup = (Cup) itr.next();
			cup.reset(this.cfg_starting_beans);
		}

		// Reset each player
		Iterator<Player> pitr = this.players.iterator();
		while (pitr.hasNext()) {
			Player player = (Player) pitr.next();
			player.reset();
		}

		// Random Start
		Integer die = 0;
		if (this.evaluation.random_boolean()){
			die = 1;
		}

		this.set_current_player(this.players.get(die));

		// Start Engine
		this.engine();			
	}

	/**
	 * Calculate the best move for the current player
	 * 
	 * @return
	 */
	public Cup help() {

		// By clicking on the active player the best move will be shown.
		this.evaluation.set_level(this.get_current_player().get_level());
		this.evaluation.evaluate();
		return this.cups.get(this.evaluation.get_best_cup());
	}

	/*
	 * Dealing Engine Starts, if its androids turn, the machine
	 */
	public void engine() {
		// As long as its android's turn, this function initialize the androids
		// choice.
		// If its a players turn, it breaks and wait on players action
	
		if(this.game_over){
			return;
		}
		
		Player player = this.get_current_player();
		
		if(player.is_android()) {
	
			// Evaluate the best turn for android
			this.evaluation.set_level(player.get_level()); 
			this.evaluation.evaluate();

			// Get the calculated cup and play it
			Cup cup = this.cups.get(this.evaluation.get_best_cup());
			if (!this.take_cup(cup)) {
				this.error("Invalid Action of Android");
				return;
			}

			// Redraw Board
			this.redraw();

			
			// Disable messages for android user (Except final message)
			if(!this.game_over){
				this.message = "";
			}
		
			// Start Engine again. Make the next move.
			this.engine();
		}
	}

	/**
	 * function for handling Bean Stealing When the last bean landed in an own,
	 * empty cup then one can steal the beans of the opposite cup
	 * 
	 * @param cup
	 */
	public void check_to_steal_beans(Cup cup) {
	
		// Check if only one bean is in the last cup and the cup is a own cup.
		if (cup.get_beans() > 1) {
			return;
		}
	
		// check if cup belongs to the current player
		if (this.is_own_cup(cup) == false) {
			return;
		}
	
		// get opposite cup
		Cup opposite_cup = this.get_opposite_cup(cup);
	
		// check if at least one bean is in opponents cup
		Integer opponents_beans = opposite_cup.fetch_beans();
		if (opponents_beans == 0) {
			return;
		}
	
		// get own can, to fill beans into it
		Cup can = this.get_current_player().get_can();
	
		// add the beans into the can (also the own one)
		Integer own_bean = cup.fetch_beans();
		Integer total_beans = (opponents_beans + own_bean);
		can.add_beans(total_beans);
	
		// Message
		this.message = total_beans.toString() + " beans were stolen";
		return;
	}

	/**
	 * Check if a player runns out of beans When one player is running out of
	 * beans, move the rest of active beans to the others can.
	 * 
	 * @return boolean, if a player run out of beans
	 */
	public boolean check_if_running_out_of_beans() {
		Boolean running_out_of_beans = true;
		Player player = null;
		
		// Check if one of the player has no beans on its side
		Iterator<Player> pitr = this.players.iterator();
		while (pitr.hasNext()) {
			running_out_of_beans = true;

			// Does the player still have some beans?
			player = (Player) pitr.next();
			Iterator<Cup> itr = player.cups.iterator();
			while (itr.hasNext()) {
				Cup cup = (Cup) itr.next();
				if (cup.get_beans() > 0) {
					running_out_of_beans = false;
					break;
				}
			}
	
			// If no beans were found, break the loop
			if (running_out_of_beans){
				break;
			}
		}
	
		if (!running_out_of_beans) {
			return false;
		}
	
		// "player" is out of beans: Get his opponent and give him the rest of
		// the beans.
		Integer handofbeans = 0;
		Player not_finisher = this.get_opponent_player(player);
		Iterator<Cup> itr = this.cups.iterator();
		while (itr.hasNext()) {
			Cup cup = (Cup) itr.next();
			if (cup.is_can() == false) {
				handofbeans += cup.fetch_beans();
			}
		}
		not_finisher.get_can().add_beans(handofbeans);
		return true;
	}

	/**
	 * Check for winner: Winner is the one who has the majority of beans
	 */
	private boolean check_for_winner() {
	
		// Balance
		Integer half_beans = this.cfg_starting_beans * 6;
		Player player = null;
	
		// is in one of the kalahas more than the half of all beans?
		// check first player
		if (this.players.get(0).get_can().get_beans() > half_beans) {
			player = this.players.get(0);
		}
	
		// check second player
		if (this.players.get(1).get_can().get_beans() > half_beans) {
			player = this.players.get(1);
		}
	
		if (player != null) {
	
			// Ok, somebody won: handle winning action
			this.message = "Congratulation: Player " + player.get_label()
					+ " won!!!!";
			this.debug("game ends by win...");
			this.game_over = true;
			player.has_won = true;
			return true;
	
		}
		
		return false;
	}

	/*
	 * Check if the game is finished and both player are equal
	 */
	private boolean check_for_draw() {
		// Check for draw: If both player half exactly the half of the points
	
		Integer half_beans = this.cfg_starting_beans * 6;
	
		// is in one of the kalahas more than the half of all beans?
		Integer player_one_beans = this.players.get(0).get_can().get_beans();
		Integer player_two_beans = this.players.get(1).get_can().get_beans();
	
		if (player_one_beans == player_two_beans
				&& player_two_beans == half_beans) {
			this.players.get(0).has_draw = true;
			this.players.get(1).has_draw = true;
	
			// A Draw
			this.message = "Finish with a borring draw!";
			this.debug("game ends by draw...");
			this.game_over = true;
			return true;
		}
		
		return false;
	}

	/**
	 * Swap current Player, if necessary
	 */
	public void check_for_changing_player(Cup last_cup) {
	
		// Landed last bean in own can? One can play again (No Player change)
		if (this.get_current_player().get_can().equals(last_cup)) {
	
			this.message = "Well done, you can play again!";
	
		} else {
	
			// Change the active Player...
			this.set_current_player(this.get_opponent_player(null));
	
		}
	}

	/**
	 * Redraw the cupboard
	 */
	public void redraw() {

		// update cups
		this.redraw_cups();
		this.redraw_players();

	}

	/**
	 * Redraw the cups.
	 */
	public void redraw_cups() {

		// set status of the cups
		Player player = this.get_current_player();

		Iterator<Cup> itr = this.cups.iterator();
		while (itr.hasNext()) {
			Cup cup = (Cup) itr.next();

			// if player is not an android enable its cups again
			if (!this.game_over && !player.is_android()
					&& player.cups.contains(cup)) {
				cup.set_enable();
			} else {
				cup.set_disable();
			}
			cup.redraw();
		}
	}

	/**
	 * Redraw the players
	 */
	public void redraw_players() {

		// update players
		Player player1 = this.players.get(0);
		Player player2 = this.players.get(1);
		Player player = null;
		
		player1.deactivate();
		player2.deactivate();

		if (this.game_over){
			// After or before the game
			if(player1.has_won){
				player = player1;
			}else if (player2.has_won){
				player = player2;
			}
		}else{
			// During the game
			player = this.get_current_player();
		}

		if(player!=null){
			player.activate();
		}
	}

	/**
	 * Return true, if cup belongs to current player
	 * 
	 * @param cup
	 * @return Boolean
	 */
	private boolean is_own_cup(Cup cup) {
		Player player = this.get_current_player();
		return (player.cups.contains(cup));
	}

	/**
	 * Set the active player (1, 2))
	 * 
	 * @param player
	 */
	public void set_current_player(Player player) {
		this.current_player = player.get_id();
	}

	/**
	 * Get the opponent player (1, 2))
	 * 
	 * @param player
	 * @return the opponent Player
	 */
	public Player get_opponent_player(Player player) {
		if (player == null) {
			player = this.get_current_player();
		}
		if (player.get_id() == 0) {
			return this.players.get(1);
		}
		if (player.get_id() == 1) {
			return this.players.get(0);
		}
		return null;
	}

	/**
	 * Get the opposite cup of the cupboard (7->5; 0->12; etc)
	 * 
	 * @param cup
	 * @return the cup instance
	 */
	
	
	private Cup get_opposite_cup(Cup cup) {
		return (this.cups.get((cup.id - 6) * (-1) + 6));
	}

	/**
	 * Return the indicated
	 * @param id
	 * @return
	 */
	public Cup get_cup(Integer id) {
		return (this.cups.get(id));
	}

	/**
	 * Return the list of cups
	 * @return
	 */
	public ArrayList<Cup> get_cups() {
		return (this.cups);
	}

	/**
	 * Add an can instance into the cup list
	 * 
	 * @param button
	 *            (Widget.Button instance)
	 * @return the Cup instance
	 */
	public Cup add_cup(Button button, boolean is_can) {

		Cup cup = new Cup(button, is_can, this.cups.size());
		this.cups.add(cup);
		return cup;
	}

	/**
	 * Add an cup instance into the cup list
	 * 
	 * @param button
	 *            (Widget.Button instance)
	 * @return the Cup instance
	 */
	public Cup add_cup(Button button) {
		return this.add_cup(button, false);
	}

	/**
	 * find the cup instance by button (e.g. after click event)
	 * 
	 * @param button
	 * @return
	 */
	public Cup find_cup_by_button(Button button) {
		this.debug("find cup by button ");

		// Iterate through all cups, to find the pushed button
		Iterator<Cup> itr = this.cups.iterator();
		while (itr.hasNext()) {
			Cup cup = (Cup) itr.next();
			if (cup.cmp_button(button)) {
				this.debug("ok, cup found: " + cup.id.toString());
				return (cup);
			}
		}
		this.debug("bad, cup not found, " + button.toString());
		return null;
	}

	/**
	 * Add a player instance into the player list
	 * 
	 * @param button
	 *            (Widget.Button instance)
	 * @return Player instance
	 */
	public Player add_player(Button button) {

		Integer player_id = this.players.size();
		Player player = new Player(button, player_id);

		// add players cups
		Integer start = player_id * 7;
		for (Integer i = start; i < start + 6; i++) {
			player.add_cup(this.cups.get(i));
		}
		// add players can
		player.set_can(this.cups.get(start + 6));

		this.players.add(player);

		return player;
	}

	
	/**
	 * Return the indicated Player
	 * @param id
	 * @return
	 */
	public Player get_player(Integer id) {
		return (this.players.get(id));
	}

	/**
	 * Return the list of players
	 * @return
	 */
	public ArrayList<Player> get_players() {
		return (this.players);
	}
	
	/**
	 * Get the number of the current player (1, 2))
	 * 
	 * @return Integer
	 */
	public Player get_current_player() {
		if (this.current_player < 0) {
			return null;
		}
		return (this.players.get(this.current_player));
	}

	/**
	 * Amount of Beans when game start
	 */
	public void set_beanamount(String beanamount) {
		// TODO: exceptions for converting
		Integer new_beanamount = Integer.parseInt(beanamount);
		this.set_beanamount(new_beanamount);
	}

	public void set_beanamount(Integer beanamount) {
		this.cfg_starting_beans = beanamount;
	}

	/**
	 * Amount of beans when a game start
	 * 
	 * @return Integer
	 */
	public Integer get_beanamount() {
		return this.cfg_starting_beans;
	}

	/**
	 * Set the flag early ending of the game; (When one player has the mayority
	 * of beans)
	 * 
	 * @param Boolean
	 *            earlyshowdown
	 */
	public void set_earlyshowdown(Boolean earlyshowdown) {
		this.cfg_early_showdown = earlyshowdown;
	}


	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Cupboard [cfg_starting_beans=" + cfg_starting_beans + ", cups="
				+ cups + ", players=" + players + ", current_player="
				+ current_player + ", is_game_finished=" + game_over
				+ "]";
	}

	
	/**
	 * Used for testing: Reset the cupboard to new values
	 * 
	 * @param cupboard
	 * @param beans
	 * @param current_player
	 * @return cupboard with new values
	 */
	public Cupboard _insert_testvalues(Cupboard cupboard,
			ArrayList<Integer> beans, Integer current_player) {
		for (Integer i = 0; i < beans.size(); i++) {
			cupboard.cups.get(i).set_beans(beans.get(i));
		}
		cupboard.set_current_player(cupboard.players.get(current_player));
		return cupboard;
	}

	/**
	 * Used for testing: Return the cup values in a array
	 * @return cup array
	 */
	public ArrayList _get_testvalues(){
		ArrayList array = new ArrayList();
		for (Integer i = 0; i < this.cups.size(); i++) {
			array.add(this.get_cup(i).get_beans());
		}
		return array;
	}
	
	/**
	 * @return the game_over
	 */
	public Boolean get_game_over() {
		return this.game_over;
	}

	/**
	 * @param game_over the game_over to set
	 */
	public void set_game_over(Boolean game_over) {
		this.game_over = game_over;
	}

	/**
	 * @return the message
	 */
	public String get_message() {
		return message;
	}

	/**
	 * @param message the message to set
	 */
	public void set_message(String message) {
		this.message = message;
	}

	/**
	 * @param random_mode the random_mode to set
	 */
	public void set_random_mode(Boolean random_mode) {
		this.random_mode = random_mode;
	}

	/**
	 * @param random_mode the random_mode to set
	 */
	public Boolean get_random_mode() {
		return this.random_mode;
	}

}
